package cfsmon.core;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.log4j.Appender;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import cfsmon.logic.dfm.DfmBackend;
import cfsmon.logic.nfm.NfmFrontend;

public class CompilerRun implements Configurator {

	private boolean forceOverwrite = false;
	private String inputFile = null;
	private String outputFile = null;
	private int monitorSlots = 1;
	private boolean abort = false;
	
	private String frontendClass = NfmFrontend.class.getName();
	private String backendClass = DfmBackend.class.getName();
	
	private static Logger logger = Logger.getLogger(CompilerRun.class);
	
	private Appender appender = null;
	private Level logLevel = Level.ALL;
	
	@Override
	public void setLogFile(String logFile) throws IOException{
		if (logFile != null){
			if (appender != null)
				appender.close();
			appender = new FileAppender(LogConfig.cfsmonLogFileLayout, logFile);
		}
	}
	
	@Override
	public void setLoglevel(Level lvl){
		logLevel = lvl;
	}
	
	@Override
	public void setInputFile(String inputFile) {
		this.inputFile = inputFile;
	}

	@Override
	public void setOutputFile(String outputFile) {
		this.outputFile = outputFile;
	}
	
	public void setBackendClass(String className) {
		this.backendClass = className;
	}
	
	public void setFrontendClass(String className){
		this.frontendClass = className;
	}

	@Override
	public void runCompiler() throws IOException{
		if (!abort){
			InputStream in = null;
			OutputStream out = null;
			
			setupLogger();
			
			try {
				
				Compiler c = new Compiler((Backend)Class.forName(backendClass).newInstance(),
						(Frontend)Class.forName(frontendClass).newInstance());
			
				in = openInput();
				out = openOutput();
				
				c.compile(in, out, monitorSlots);	
								
			} catch (IOException ie){
				; // log entry already created
				logger.info("Aborting compilation.");
			} catch (Exception e){
				logger.error("An exception occurred in the compiler run: ", e);
				logger.info("Aborting compilation.");
			}
			
			closeStreams(in, out);			
		}
	}
	
	private void setupLogger(){
		if (appender == null){
			appender = new ConsoleAppender(LogConfig.cfsmonLogConsoleLayout);
		}
		// append to root logger
		Logger.getRootLogger().addAppender(appender);
		Logger.getRootLogger().setLevel(logLevel);
	}
	
	private InputStream openInput() throws Exception{
		InputStream in = null;
		if (inputFile == null){
			in = System.in;
			logger.info("Input is read from stdin.");
		} else {
			try{
				in = new FileInputStream(inputFile);					
			} catch (FileNotFoundException e){
				logger.error("The input file " + inputFile + " could not be found.");
				throw e;
			}
		}
		return in;
	}
	
	private OutputStream openOutput() throws Exception{
		OutputStream out = null;
		if (outputFile == null){
			out = System.out;
			logger.info("Output is written to stdout.");
		} else {
			File f = new File(outputFile).getAbsoluteFile();
			if (!f.exists()){
				if (f.getParentFile() != null && !f.getParentFile().exists()){
					if (!f.getParentFile().mkdirs()){
						logger.error("The path to the output file " + outputFile + " could not be created.");
						throw new FileNotFoundException(outputFile + " could not be created.");
					}
				}
				try{
					f.createNewFile();				
				} catch (Exception e){
					logger.error("The output file " + outputFile + " could not be created.");
					throw e; 
				}
			} else {
				if (!forceOverwrite){
					logger.error("The output file " + outputFile + " already exists. Remove it first or use the force overwrite flag.");						
					throw new IOException(outputFile + " already exists.");
				} else {
					logger.info("The output file " + outputFile + " exists and will be overwritten.");
				}
			}
			assert(f.exists()); // and may overwrite
			
			out = new FileOutputStream(f);
		}
		return out;
	}
	
	private void closeStreams(InputStream in, OutputStream out){
		if (in != null && in != System.in){
			try {in.close(); } catch (Exception e){};
		}
		if (out != null && out != System.out){
			try {out.close(); } catch (Exception e){};
		}
	}

	@Override
	public void abortConfig() {
		abort = true;
	}

	@Override
	public boolean isAborted() {
		return abort;
	}

	@Override
	public void forceOverwrite() {
		forceOverwrite = true;
	}

	@Override
	public void setNumberOfMonitorSlots(int slots) {
		if (slots < 1){
			logger.warn("Attempt to set the maximum number of monitors to <" + slots + "> failed. The current value of <" + monitorSlots + "> has been retained." );
			return;
		}
		monitorSlots = slots;
		logger.debug("Number of monitor slots is set to <" + monitorSlots + ">.");
	}
	
}
